Estudiante: Sebastián Cardona y Jose Miguel Millán
ID: 1094910122 y 1088334182
Email: sacardonar@uqvirtual.edu.co y josem.millanl@uqvirtual.edu.co
**Ponga su nombre en el archivo de Jupyter Notebook Docente: [Jose R. Zapata](https://joserzapata.github.io)
El objetivo inicial del trabajo es utilizar Python para el procesamiento, descripcion y visualización de datos, con el fin de dejar los datos preparados para ser usados con algoritmos de Machine Learning para Regresión o Clasificación como objetivo final del trabajo.
El trabajo se realizara en Python usando un jupyter notebook o un Notebook de Google Collaboratory, llenando los campos de este archivo y subirlo a github.
Preparar los datos, hacer cada uno de los pasos solo si es necesario:
Realizar una exploración estadística y con visualización de los datos
Entrene y Evalue modelos de Machine Learning según sea su problema, recuerde usar k-fold cross-validation para evitar over fitting, algunos algoritmos recomendados
(a) PREDICCIÓN:
▪ Regresión Lineal
▪ Lasso
▪ Ridge
▪ Decision Tree regressor
▪ Random Forest regressor
▪ SVR
▪ KNR
▪ catboost regressor
(b) CLASIFICACION
▪ Regresión Logística
▪ LinearSVC
▪ KernelSVC
▪ Decision Tree clasifier
▪ Random Forest classifier
▪ K-NN
▪ GaussianNB
▪ catboost
Nota: utilizar visualizaciones para comunicar los resultados obtenidos.
Realizar hyperparameter optimization de los dos métodos que tengan mejor resultado en los pasos anteriores para mejorar el desempeño obtenido. (Tenga en cuenta, que realizar el hyperparameter tuning no garantiza una mejora significativa en el desempeño, pero es bueno intentarlo)
Realizar comentarios de cada paso que va realizando en el documento y finalmente también agregue conclusiones de:
*NOTA: No dude en contactarme para cualquier pregunta o inquietud :)
EL jupyter notebook estara alojado en un repositorio de Github
El código debe tener comentarios y explicaciones de la solución del trabajo.
(33 % ) preparacion, descripcion y visualizacion de datos
(33 % ) Machine Learning, hiperparametrizacion y Conclusiones
|Porcentaje en la evaluación | Descripción| Nada | Incompleto | Completo |
| :---: |:---: |:---: |:---: |:---: |
| 11 % |Preparación de los datos
(datos nulos, outliers, duplicados,
corrección de formatos de tipos de datos) |
|11 % | Visualización de datos
(Univarible y Bivariable)
Histogramas, boxplots, correlaciones, etc|
|11 % | Descripción y Análisis Estadístico de los datos |
|11 % | Machine Learning
Entrenar y evaluar todos los modelos propuestos |
|11 % | Hiper parametrizacion
Hiperparametrizar 2 modelos y escoger el mejor modelo |
|11 % | Resultados y Conclusiones
analisis de los datos, conclusiones del modelos y los resultados obtenidos|
Ejemplos y links de ayuda al final del documento
El dataset "house data", inicialmente se realizará una exploración de datos, para poder saber la calidad del dataset, iniciando con una limpieza la cual consta de eliminar duplicados, identificación de datos atípicos, nullos o mal escritos para poder tratarlos y mitigarlos, ya sea con la eliminación o aplicación de métodos estadísticos, con la finalidad de tener un datset listo y poder aplicar una regresión lineal y poder predecir los precios de venta de una casa.
from jutils.data import DataUtils
from jutils.visual import Plot
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from pandas_profiling.profile_report import ProfileReport
from pathlib import Path
import pandas as pd
import numpy as np
from cache_to_disk import cache_to_disk
from IPython.display import display, HTML
import plotly.express as px
import seaborn as sns
from sklearn.preprocessing import PowerTransformer, KBinsDiscretizer, OneHotEncoder
from scipy import stats
import pingouin as pg
from sklearn.linear_model import SGDRegressor
from sklearn.svm import SVR
from sklearn.model_selection import cross_val_score
def validar_duplicados(_df):
_filas = _df.shape[0]
_cant_duplicados = _df.duplicated().sum()
print(f'De {_filas} registros hay {_cant_duplicados} filas duplicadas, representando el {_cant_duplicados/_filas:.2%}')
def eliminar_duplicados(_df):
# Eliminando duplicados
_df = _df.drop_duplicates(keep='first')
_filas = _df.shape[0]
print(f'Después de la eliminación de duplicados, el conjunto de datos queda con {_filas} filas.')
return _df
def validar_index_duplicados(_df):
# Validando duplicados de index
_son_duplicados = _df['index'].duplicated()
_cant_duplicados = _son_duplicados.sum()
_filas = _df.shape[0]
print(f'De {_filas} registros, hay {_cant_duplicados} registros con index duplicado, que representan el {_cant_duplicados/_filas:.2%}.')
return _son_duplicados
@cache_to_disk(1)
def profiler_to_file(_profiler, archivo):
print('Ejecutando profiler')
_profiler.to_file(du.data_folder_path.parent.joinpath('reports/' + archivo))
return True
def calcular_descriptivas(_df):
descriptivas = _df.describe()
descriptivas.loc['rango'] = descriptivas.loc['max'] - descriptivas.loc['min']
descriptivas.loc['IQR'] = descriptivas.loc['75%'] - descriptivas.loc['25%']
descriptivas.loc['coef de var'] = descriptivas.loc['std']/descriptivas.loc['mean']
descriptivas.loc['skewness'] = du.data.skew(numeric_only=True)
descriptivas.loc['kurtosis'] = du.data.kurtosis(numeric_only=True)
return descriptivas
du = DataUtils(
Path(r'..\data').resolve().absolute(),
"kc_house_dataDS.parquet",
lambda path: pd.read_parquet(path),
lambda df, path: df.to_parquet(path)
)
du.data = du.load_data(du.interim_path.joinpath(du.input_file_name))
Durante la exploración inicial se realizó la conversión de los tipos de datos, y la correcta representación de datos nulos.
shape = du.data.shape
filas = shape[0]
columnas = shape[1]
print(f'El conjunto de datos se compone de {filas} filas y {columnas} columnas.')
El conjunto de datos se compone de 131994 filas y 23 columnas.
du.data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 131994 entries, 0 to 131993 Data columns (total 23 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 index 131994 non-null int64 1 zipcode 118787 non-null float64 2 grade 118717 non-null float64 3 sqft_basement 118688 non-null float64 4 view 118854 non-null float64 5 bathrooms 118689 non-null float64 6 bedrooms 118729 non-null float64 7 sqft_above 118712 non-null float64 8 sqft_living15 118814 non-null float64 9 lat 118731 non-null float64 10 waterfront 118730 non-null float64 11 floors 118791 non-null float64 12 date 127544 non-null object 13 yr_renovated 118728 non-null float64 14 yr_built 118712 non-null float64 15 long 118760 non-null float64 16 jhygtf 118728 non-null float64 17 sqft_lot 118763 non-null float64 18 price 118661 non-null float64 19 condition 118740 non-null float64 20 wertyj 131994 non-null int64 21 sqft_lot15 118739 non-null float64 22 sqft_living 118768 non-null float64 dtypes: float64(20), int64(2), object(1) memory usage: 24.2+ MB
Todas las columnas son del tipo correcto a excepción de date, se deberá hacer la conversión de este campo.
validar_duplicados(du.data)
De 131994 registros hay 1300 filas duplicadas, representando el 0.98%
du.data = eliminar_duplicados(du.data)
Después de la eliminación de duplicados, el conjunto de datos queda con 130694 filas.
# Validar indices duplicados
son_duplicados = validar_index_duplicados(du.data)
De 130694 registros, hay 108695 registros con index duplicado, que representan el 83.17%.
# Revisando los primeros registros duplicados
du.data[son_duplicados].sort_values(by='index').head()
| index | zipcode | grade | sqft_basement | view | bathrooms | bedrooms | sqft_above | sqft_living15 | lat | ... | yr_renovated | yr_built | long | jhygtf | sqft_lot | price | condition | wertyj | sqft_lot15 | sqft_living | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 44480 | 0 | 98178.0 | 7.0 | 0.0 | 0.0 | 1.0 | NaN | 1.180000e+03 | 1.340000e+03 | 47.5112 | ... | 0.0 | 1.955000e+03 | NaN | 0.0 | 5.650000e+03 | 221900.0 | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 59332 | 0 | 98178.0 | 7.0 | NaN | NaN | 1.0 | 3.0 | 1.180000e+03 | 1.340000e+03 | 47.5112 | ... | 0.0 | 1.955000e+03 | -122257.0 | 0.0 | 5.650000e+03 | NaN | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 89550 | 0 | NaN | NaN | 0.0 | 0.0 | 1.0 | 3.0 | 1.180000e+03 | NaN | NaN | ... | 0.0 | NaN | -122257.0 | 0.0 | 5.650000e+03 | 221900.0 | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 27240 | 0 | 98178.0 | 7.0 | 0.0 | 0.0 | 1.0 | 3.0 | 1.180000e+03 | 1.340000e+03 | 47.5112 | ... | 0.0 | 1.955000e+03 | -122257.0 | 0.0 | 5.650000e+03 | 221900.0 | 3.000000e+00 | 876543 | 5650.0 | 1180.0 |
| 50303 | 0 | 98178.0 | 7.0 | 0.0 | 0.0 | 1.0 | 3.0 | -5.432346e+10 | -5.432346e+10 | 47.5112 | ... | 0.0 | -5.432346e+10 | -122257.0 | 0.0 | -5.432346e+10 | 221900.0 | -5.432346e+10 | 876543 | 5650.0 | 1180.0 |
5 rows × 23 columns
Revisando los registros duplicados por index, se encuentra que muchas columnas tienen los mismos valores , lo único que cambia es que hay algunos faltantes y hay otros valores extremadamente bajos o altos, adicionalmente se observan algunos registros de la columna date que no son fechas. Primero se convertirá los valores de la columna date a date y los que no puedan ser convertidos se reemplazarán por valores nulos, luego se reemplazarán los valores extremos por valores nulos, luego se calculará la mediana por index para las columnas numéricas y se reemplazarán los valores nulos por estas medianas. Luego se eliminarán filas duplicadas y se reevaluarán los index duplicados.
# Convirtiendo la columna date a datetime
du.data['date'] = pd.to_datetime(du.data['date'], errors='coerce')
# Reemplazando valores extremos, menores a -1e+10 o mayores a 1e+10
columnas_numericas = [columna for columna in du.data.columns if columna != 'date']
du.data[columnas_numericas] = du.data[columnas_numericas].where(lambda x: x > -1e+10, other=np.nan).where(lambda x: x < 1e+10, other=np.nan)
# Se reemplazan los valores extremos por la media
for columna_numerica in columnas_numericas:
du.data[columna_numerica]=du.data[columna_numerica].fillna(du.data.groupby('index')[columna_numerica].transform('median'))
# Reemplazando fechas nulas por la primera fecha no nula
du.data['date'] = du.data['date'].fillna(du.data.groupby(['index'], sort=False)['date'].apply(lambda x: x.ffill().bfill()))
Para las siguientes columnas, un cero representa un dato nulo, por lo tanto se reemplazarán.
# Reemplazando ceros por valores nulos
du.data[['sqft_basement', 'yr_renovated']] = du.data[['sqft_basement', 'yr_renovated']].replace(0, np.nan)
validar_duplicados(du.data)
De 130694 registros hay 108695 filas duplicadas, representando el 83.17%
du.data = eliminar_duplicados(du.data)
Después de la eliminación de duplicados, el conjunto de datos queda con 21999 filas.
son_duplicados = validar_index_duplicados(du.data)
De 21999 registros, hay 0 registros con index duplicado, que representan el 0.00%.
# Validando columnas con valores constantes
unicos=du.data.nunique()
unicos[unicos==1]
wertyj 1 dtype: int64
La columna wertyj tiene valores constantes, por lo tanto se eliminará.
du.data.drop(columns=list(unicos[unicos==1].index), inplace=True)
nulos = du.data.isnull().sum()
porce = nulos/filas
nulos = pd.DataFrame({'nulos':nulos, 'porc':porce})
# Se contarán las filas que contengan algún dato nulo
al_menos_un_nulo=du.data.isnull().any(axis=1).sum()
nulos
| nulos | porc | |
|---|---|---|
| index | 0 | 0.000000 |
| zipcode | 12 | 0.000091 |
| grade | 9 | 0.000068 |
| sqft_basement | 13368 | 0.101277 |
| view | 4 | 0.000030 |
| bathrooms | 8 | 0.000061 |
| bedrooms | 5 | 0.000038 |
| sqft_above | 12 | 0.000091 |
| sqft_living15 | 1 | 0.000008 |
| lat | 9 | 0.000068 |
| waterfront | 6 | 0.000045 |
| floors | 9 | 0.000068 |
| date | 3 | 0.000023 |
| yr_renovated | 21069 | 0.159621 |
| yr_built | 7 | 0.000053 |
| long | 3 | 0.000023 |
| jhygtf | 9 | 0.000068 |
| sqft_lot | 6 | 0.000045 |
| price | 30 | 0.000227 |
| condition | 5 | 0.000038 |
| sqft_lot15 | 1 | 0.000008 |
| sqft_living | 11 | 0.000083 |
print(f'De {filas} registros, hay {al_menos_un_nulo} registros con al menos un valor nulo, representando el {al_menos_un_nulo/filas:.2%}')
De 131994 registros, hay 21531 registros con al menos un valor nulo, representando el 16.31%
Este paso debe ser al inicio de los proyectos, Se deben realizar todas las transformaciones, preparación de datos y limpieza de los datos, en el train set y en la evaluacion se deben aplicarl al test set y a los datos nuevos que lleguen al sistema. Esta division inicial se hace para evitar data leakage de los datos de test a los datos de train, por ejemplo en las imputaciones.
Por este motivo se realizara en esta parte.
Division de los datos de entrenamiento (Train set) y de Evaluacion (test - set), las divisiones utilizadas son
El Train set se hace para seleccion de Modelos y el Test-set solamente para la evaluacion Final
train_test, validation = train_test_split(du.data, test_size=0.8, random_state=1)
du.save_data(train_test, du.raw_train_test_path)
du.save_data(validation, du.raw_validation_path)
du.data = du.load_data(du.raw_train_test_path)
print('Tipos de variables')
du.data.info()
Tipos de variables <class 'pandas.core.frame.DataFrame'> Int64Index: 4399 entries, 32828 to 235 Data columns (total 22 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 index 4399 non-null int64 1 zipcode 4395 non-null float64 2 grade 4398 non-null float64 3 sqft_basement 1722 non-null float64 4 view 4399 non-null float64 5 bathrooms 4397 non-null float64 6 bedrooms 4399 non-null float64 7 sqft_above 4395 non-null float64 8 sqft_living15 4398 non-null float64 9 lat 4399 non-null float64 10 waterfront 4399 non-null float64 11 floors 4397 non-null float64 12 date 4398 non-null datetime64[ns] 13 yr_renovated 212 non-null float64 14 yr_built 4399 non-null float64 15 long 4399 non-null float64 16 jhygtf 4396 non-null float64 17 sqft_lot 4398 non-null float64 18 price 4396 non-null float64 19 condition 4399 non-null float64 20 sqft_lot15 4399 non-null float64 21 sqft_living 4398 non-null float64 dtypes: datetime64[ns](1), float64(20), int64(1) memory usage: 790.4 KB
columnas_no_entrada = {'date', 'index'}
entradas = [column for column in du.data.columns if column not in columnas_no_entrada]
print('Columnas de entrada:')
print(', '.join(entradas))
Columnas de entrada: zipcode, grade, sqft_basement, view, bathrooms, bedrooms, sqft_above, sqft_living15, lat, waterfront, floors, yr_renovated, yr_built, long, jhygtf, sqft_lot, price, condition, sqft_lot15, sqft_living
salida = 'date'
print(f'Columna de salida: {salida}')
Columna de salida: date
Analisis de cada una de las variables para lograr calidad de datos en cada columna
Se realizará un perfilado de los datos para identificar problemas de calidad no identificados anteriormente.
columnas_entrenamiento = entradas + [salida]
profiler = ProfileReport(du.data[columnas_entrenamiento], explorative=True)
profiler_to_file(profiler, '1_0.html')
True
graficar = Plot()
graficar.box(du.data, y='sqft_lot15')
| Columna | Tipo | Problemas de calidad encontrados | Correcciones | |---------------|--------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | df_index | | | | | zipcode | | | Se eliminará | | grade | Categórica | | | | sqft_basement | Numérica | Muchos datos nulos | Se encontró el 60.8% nulos, se transformará esta columna en una columna categórica binaria que diga si tiene sótano o no (Si el valor es nulo no tiene, de lo contrario si tiene). | | view | Categórica | | | | bathrooms | Numérica | | | | bedrooms | Numérica | | | | sqft_above | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | sqft_living15 | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | lat | | | Se eliminará, esta columna solo representa una posición, y como todas las casas están en la misma ciudad, es la misma latitud. | | waterfront | Categórica | | | | floors | Numérica | | | | date | fecha | | Se utilizará esta columna para calcular la cantidad de años transcurridos desde la construcción de la propiedad hasta la venta de la misma. | | yr_renovated | Numérica | | Se eliminará, será reemplazada por una columna que indique si fue renovada o no sin importar el año. | | yr_built | | | Se utilizará para calcular los años transcurridos desde la construcción hasta la venta de la propiedad, luego será eliminada. | | long | | | Se eliminará, esta columna solo representa una posición, y como todas las casas están en la misma ciudad, es la misma longitud. | | jhygtf | | | Es la misma columna que yr_renovated, será eliminada. | | sqft_lot | Numérica | Se encontraron datos atípicos. | Se encontró que el dato mas grande es de 1 millón de pies cuadrados que equivalen a aproximadamente 10 hectáreas, se considera que una propiedad puede tener estas dimensiones, por lo tanto no se considera un error. Se aplicará logaritmo y se validará si la distribución se normaliza, de lo contrario, se convertirá a categórica dividiéndola en rangos de tamaño. | | price | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | condition | Categórica nominal | | Se aplicará one hot encoding | | sqft_lot15 | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. | | sqft_living | Numérica | Tiene una distribución asimétrica | Se transformará para normalizar la distribución. |
variables_categoricas = ['grade', 'view', 'waterfront', 'condition']
du.data[variables_categoricas] = du.data[variables_categoricas].astype("category")
# Años transcurridos entre la construcción de la vivienda y la venta de la misma columnas yr_built y date
# Primero se extraerá el año de la fecha para realizar la resta con yr_built
du.data['yr_date'] = du.data['date'].dt.year
du.data['antiguedad_venta'] = du.data['yr_date'] - du.data['yr_built']
du.data.drop(columns=['yr_date', 'date', 'yr_built'], inplace=True)
du.data.drop(columns=['zipcode', 'lat', 'yr_renovated', 'long', 'jhygtf'], inplace=True)
variables_numericas = ['sqft_basement', 'bathrooms', 'bedrooms', 'sqft_above', 'sqft_living15', 'floors', 'sqft_lot', 'price', 'sqft_lot15',
'sqft_living', 'antiguedad_venta']
columnas_datosFaltantes = [columna for columna in du.data.columns if columna != 'date' and columna != 'sqft_basement' and columna != 'yr_renovated' ]
du.data=du.data.dropna(subset=columnas_datosFaltantes)
du.data.isnull().sum().sort_values(ascending=False)
sqft_basement 2668 index 0 grade 0 view 0 bathrooms 0 bedrooms 0 sqft_above 0 sqft_living15 0 waterfront 0 floors 0 sqft_lot 0 price 0 condition 0 sqft_lot15 0 sqft_living 0 antiguedad_venta 0 dtype: int64
Estadistico Descriptico y Analisis
| Tendencia Central | Medida de Dispersión | Visualizacion | |:-----------------:|:-------------------------:|:-------------:| | Media | Rango | Histogramas | | Mediana | Cuartiles | Boxplots | | Moda | Rango inter cuartil (IQR) | | | Minimo | Varianza | | | Maximo | Desviacion Estandard | | | . | Skewness | | | . | Kurtosis | |
calcular_descriptivas(du.data)
| index | sqft_basement | bathrooms | bedrooms | sqft_above | sqft_living15 | floors | sqft_lot | price | sqft_lot15 | sqft_living | antiguedad_venta | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 4384.000000 | 1716.000000 | 4384.000000 | 4384.000000 | 4384.000000 | 4384.000000 | 4384.000000 | 4.384000e+03 | 4.384000e+03 | 4384.000000 | 4384.000000 | 4384.000000 |
| mean | 10927.939097 | 741.649184 | 2.111656 | 3.367245 | 1776.135493 | 1987.938869 | 1.484261 | 1.424680e+04 | 4.028694e+07 | 12266.174270 | 2066.434307 | 43.431113 |
| std | 6280.360000 | 399.057931 | 0.770968 | 0.907386 | 807.552700 | 680.565380 | 0.538801 | 3.647116e+04 | 2.463024e+08 | 26861.322559 | 897.047578 | 29.188176 |
| min | 1.000000 | 10.000000 | 0.500000 | 1.000000 | 420.000000 | 720.000000 | 1.000000 | 6.090000e+02 | 8.995000e+04 | 748.000000 | 420.000000 | -1.000000 |
| 25% | 5473.500000 | 450.000000 | 1.750000 | 3.000000 | 1200.000000 | 1500.000000 | 1.000000 | 5.081500e+03 | 3.199500e+05 | 5110.000000 | 1420.000000 | 18.000000 |
| 50% | 10775.500000 | 700.000000 | 2.250000 | 3.000000 | 1560.000000 | 1830.000000 | 1.000000 | 7.681500e+03 | 4.500000e+05 | 7683.000000 | 1900.000000 | 40.000000 |
| 75% | 16344.750000 | 970.000000 | 2.500000 | 4.000000 | 2190.000000 | 2360.000000 | 2.000000 | 1.068325e+04 | 6.500000e+05 | 10057.250000 | 2547.750000 | 63.000000 |
| max | 21985.000000 | 3480.000000 | 8.000000 | 9.000000 | 8570.000000 | 5790.000000 | 3.500000 | 1.074218e+06 | 3.635000e+09 | 871200.000000 | 12050.000000 | 115.000000 |
| rango | 21984.000000 | 3470.000000 | 7.500000 | 8.000000 | 8150.000000 | 5070.000000 | 2.500000 | 1.073609e+06 | 3.634910e+09 | 870452.000000 | 11630.000000 | 116.000000 |
| IQR | 10871.250000 | 520.000000 | 0.750000 | 1.000000 | 990.000000 | 860.000000 | 1.000000 | 5.601750e+03 | 3.300500e+05 | 4947.250000 | 1127.750000 | 45.000000 |
| coef de var | 0.574707 | 0.538068 | 0.365101 | 0.269474 | 0.454668 | 0.342347 | 0.363010 | 2.559954e+00 | 6.113704e+00 | 2.189870 | 0.434104 | 0.672057 |
| skewness | 0.032331 | 1.041309 | 0.549007 | 0.411545 | 1.371571 | 1.092855 | 0.650255 | 1.297772e+01 | 6.738575e+00 | 13.287885 | 1.388833 | 0.450191 |
| kurtosis | -1.189729 | 2.657962 | 1.625505 | 1.196312 | 3.100660 | 1.456801 | -0.451702 | 2.691536e+02 | 4.994818e+01 | 304.005651 | 5.344829 | -0.666037 |
for variable_numerica in variables_numericas:
graficar.box(du.data, y=variable_numerica).show()
for variable_numerica in variables_numericas:
graficar.histogram(du.data, x=variable_numerica, nbins=5).show()
resumen = []
for variable_categorica in variables_categoricas:
col = du.data[variable_categorica]
elms_cat = col.groupby(by=col).agg('count')
total = elms_cat.sum()
porc = elms_cat / total
porc.name = 'porc'
df = pd.DataFrame([elms_cat, porc]).transpose()
resumen.append(df)
for tabla in resumen:
display(HTML(tabla.to_html()))
variable = tabla.columns[0]
fig = px.bar(tabla[variable], orientation='h', title=str(variable))
fig.show()
C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning: Index.ravel returning ndarray is deprecated; in a future version this will return a view on self. C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning: Index.ravel returning ndarray is deprecated; in a future version this will return a view on self. C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning: Index.ravel returning ndarray is deprecated; in a future version this will return a view on self. C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\2751080646.py:5: FutureWarning: Index.ravel returning ndarray is deprecated; in a future version this will return a view on self.
| grade | porc | |
|---|---|---|
| grade | ||
| 3.0 | 1.0 | 0.000228 |
| 4.0 | 3.0 | 0.000684 |
| 5.0 | 60.0 | 0.013686 |
| 6.0 | 407.0 | 0.092838 |
| 7.0 | 1828.0 | 0.416971 |
| 8.0 | 1228.0 | 0.280109 |
| 9.0 | 508.0 | 0.115876 |
| 10.0 | 254.0 | 0.057938 |
| 11.0 | 76.0 | 0.017336 |
| 12.0 | 16.0 | 0.003650 |
| 13.0 | 3.0 | 0.000684 |
| view | porc | |
|---|---|---|
| view | ||
| 0.0 | 3966.0 | 0.904653 |
| 1.0 | 72.0 | 0.016423 |
| 2.0 | 189.0 | 0.043111 |
| 3.0 | 90.0 | 0.020529 |
| 4.0 | 67.0 | 0.015283 |
| waterfront | porc | |
|---|---|---|
| waterfront | ||
| 0.0 | 4349.0 | 0.992016 |
| 1.0 | 35.0 | 0.007984 |
| condition | porc | |
|---|---|---|
| condition | ||
| 1.0 | 7.0 | 0.001597 |
| 2.0 | 35.0 | 0.007984 |
| 3.0 | 2884.0 | 0.657847 |
| 4.0 | 1112.0 | 0.253650 |
| 5.0 | 346.0 | 0.078923 |
Estadistico Descriptico y Analisis
corr_matrix = du.data.corr()
corr_matrix['price'].sort_values(ascending=False)
price 1.000000 sqft_living 0.298693 sqft_above 0.277249 sqft_living15 0.260434 bathrooms 0.220781 sqft_basement 0.144820 floors 0.126196 bedrooms 0.111953 sqft_lot15 0.024541 sqft_lot 0.016790 antiguedad_venta -0.000036 index -0.017693 Name: price, dtype: float64
Las variables relacionadas con el tamaño del apartamento tienen mayor correlación con el precio del apartamento, se observa que la antiguedad de venta, no tiene correlación con el precio, por lo tanto esta columna será eliminada.
du.data.drop(columns='antiguedad_venta', inplace=True)
# plot it
sns.heatmap(corr_matrix, cmap='PuOr')
<AxesSubplot:>
fig, ax = plt.subplots(figsize=(12, 8))
plt.scatter(du.data['price'], du.data['bedrooms'])
plt.xlabel('precio')
plt.ylabel('cantidad de baños')
plt.show()
graficar.scatter(du.data,x='price',y='sqft_lot')
Se pueden resaltar dos datos atípicos, una casa muy económica y grande (USD 937000 y 871000 sqft) y una casa muy costosa y pequeña (USD 3000000000 y 19897 sqft), estos datos se consideran atípicos pero que pueden ocurrir en la realidad. Adicionalmente se encuentran dos grupos de apartamentos, el primer grupo son casas que cuestan menos de 8 millones y tienen áreas hasta de alrededor de 1 millón de pies cuadrados, unas 10 hectáreas. El otro grupo son casas entre 1000 millones y 3000 millones con áreas hasta de 0.2 millónes de pies cuadrados, unas 2 hectáreas. Inicialmente se construirá un primer modelo con ambos grupos juntos, y se evaluará su desempeño.
# Diccionario donde se almacenaran los transformadores
transformers = {}
# Se considerará que un apartamento tiene sótano si sqft_basement está nulo.
du.data['tiene_sotano'] = du.data['sqft_basement'].notna()
# Reemplazando true por 1 y false por 0
du.data['tiene_sotano'] = du.data['tiene_sotano'].astype('int').astype('category')
du.data.drop(columns='sqft_basement', inplace=True)
# Se transformarán las siguientes variables: sqft_above, sqft_living15, sqft_lot, price, sqft_lot15, sqft_living
columnas = ['sqft_above', 'sqft_living15', 'sqft_lot', 'price', 'sqft_lot15', 'sqft_living']
pt = PowerTransformer(method='box-cox')
pt.fit(du.data[columnas])
transformers['PowerTransformer'] = pt
du.data[columnas] = pt.transform(du.data[columnas])
profiler2 = ProfileReport(du.data[columnas], explorative=True)
profiler_to_file(profiler2, 'power_transform.html')
True
# Se revisará nuevamente la distribución de las variables con box plots e histogramas
for variable_numerica in columnas:
graficar.box(du.data[columnas], y=variable_numerica).show()
for variable_numerica in columnas:
graficar.histogram(du.data[columnas], x=variable_numerica, nbins=20).show()
calcular_descriptivas(du.data[columnas])
| sqft_above | sqft_living15 | sqft_lot | price | sqft_lot15 | sqft_living | |
|---|---|---|---|---|---|---|
| count | 4.384000e+03 | 4.384000e+03 | 4.384000e+03 | 4.384000e+03 | 4.384000e+03 | 4.384000e+03 |
| mean | -1.831868e-15 | 1.236237e-14 | -2.129683e-15 | -1.020752e-12 | 5.262619e-15 | -1.329026e-16 |
| std | 1.000114e+00 | 1.000114e+00 | 1.000114e+00 | 1.000114e+00 | 1.000114e+00 | 1.000114e+00 |
| min | -3.728512e+00 | -3.363023e+00 | -3.780775e+00 | -4.669308e+00 | -3.862709e+00 | -3.467887e+00 |
| 25% | -6.910058e-01 | -6.838228e-01 | -4.741043e-01 | -6.054371e-01 | -4.934459e-01 | -6.860350e-01 |
| 50% | -4.132344e-02 | -4.577595e-02 | 3.529345e-02 | 5.071293e-02 | 6.845990e-02 | 6.785413e-04 |
| 75% | 7.417311e-01 | 7.214138e-01 | 4.155467e-01 | 6.217542e-01 | 4.149351e-01 | 7.011788e-01 |
| max | 3.333081e+00 | 3.039771e+00 | 3.917704e+00 | 2.901085e+00 | 4.097453e+00 | 4.561636e+00 |
| rango | 7.061593e+00 | 6.402794e+00 | 7.698479e+00 | 7.570393e+00 | 7.960162e+00 | 8.029523e+00 |
| IQR | 1.432737e+00 | 1.405237e+00 | 8.896511e-01 | 1.227191e+00 | 9.083810e-01 | 1.387214e+00 |
| coef de var | -5.459531e+14 | 8.089984e+13 | -4.696069e+14 | -9.797820e+11 | 1.900411e+14 | -7.525165e+15 |
| skewness | 1.496731e-02 | 9.800042e-03 | -1.026882e-01 | 2.751068e-02 | -1.151754e-01 | -1.103435e-03 |
| kurtosis | -4.306862e-01 | -3.124572e-01 | 2.002935e+00 | 1.346496e+00 | 2.226061e+00 | -1.785442e-01 |
Después de realizar la transformación de las variables, se encuentra que sqft_lot y sqft_lot15 siguen teniendo una alta kurtosis, por lo tanto se procederá a convertirlas en variables categóricas.
columnas_a_categoricas = ['sqft_lot', 'sqft_lot15']
kbd = KBinsDiscretizer(strategy='kmeans', encode='ordinal')
kbd.fit(du.data[columnas_a_categoricas])
transformers['KBinsDiscretizer'] = kbd
du.data[columnas_a_categoricas] = kbd.transform(du.data[columnas_a_categoricas])
du.data[columnas_a_categoricas] = du.data[columnas_a_categoricas].astype('category')
profiler3 = ProfileReport(du.data, explorative=True)
profiler_to_file(profiler3, '2_0_transformacion_final.html')
profiler3
Summarize dataset: 0%| | 0/5 [00:00<?, ?it/s]
Generate report structure: 0%| | 0/1 [00:00<?, ?it/s]
Render HTML: 0%| | 0/1 [00:00<?, ?it/s]
df= du.data[['grade','view','condition']]
sns.distplot(df)
C:\Users\jevo1\Documents\Python Scripts\trabajo_ciencia_de_datos_1\venv\lib\site-packages\seaborn\distributions.py:2619: FutureWarning: `distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms).
<AxesSubplot:ylabel='Density'>
du.data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 4384 entries, 32828 to 235 Data columns (total 15 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 index 4384 non-null int64 1 grade 4384 non-null category 2 view 4384 non-null category 3 bathrooms 4384 non-null float64 4 bedrooms 4384 non-null float64 5 sqft_above 4384 non-null float64 6 sqft_living15 4384 non-null float64 7 waterfront 4384 non-null category 8 floors 4384 non-null float64 9 sqft_lot 4384 non-null category 10 price 4384 non-null float64 11 condition 4384 non-null category 12 sqft_lot15 4384 non-null category 13 sqft_living 4384 non-null float64 14 tiene_sotano 4384 non-null category dtypes: category(7), float64(7), int64(1) memory usage: 339.7 KB
variables_entrada = ['grade', 'view', 'bathrooms', 'bedrooms', 'sqft_above', 'sqft_living15', 'waterfront', 'floors', 'sqft_lot', 'condition', 'sqft_lot15', 'sqft_living', 'tiene_sotano']
variable_salida = 'price'
du.data = du.data[variables_entrada + [variable_salida]]
# Variables categóricas con las que se realizará onehotencoding
columnas_one_hot = ['grade', 'view', 'waterfront', 'sqft_lot', 'condition', 'sqft_lot15', 'tiene_sotano']
ohe = OneHotEncoder(sparse=False)
ohe.fit(du.data[columnas_one_hot])
transformers['OneHotEncoder'] = ohe
temp = pd.DataFrame(ohe.transform(du.data[columnas_one_hot]), columns=ohe.get_feature_names_out(), index=du.data.index).copy()
du.data.drop(columns=columnas_one_hot, inplace=True)
C:\Users\jevo1\AppData\Local\Temp\ipykernel_13952\3481676883.py:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
du.data = pd.concat([du.data, temp], axis=1)
y_name = 'price'
x_names = [columna for columna in du.data.columns if not columna == 'price']
Se hace seleccion de los mejores modelos usando el Training Set y k-fold Cross Validation
modelos_a_probar = [('SGD',SGDRegressor()), ('SVR',SVR())]
scores = {}
for modelo in modelos_a_probar:
scores[modelo[0]] = {}
scores[modelo[0]]['scores'] = cross_val_score(modelo[1], du.data[x_names], du.data[y_name], cv=5, scoring='r2')
scores[modelo[0]]['media'] = np.mean(scores[modelo[0]]['scores'])
scores[modelo[0]]['std'] = np.std(scores[modelo[0]]['scores'])
scores[modelo[0]]['coef. var.'] = scores[modelo[0]]['std'] / scores[modelo[0]]['media']
modelos_a_probar[0][1].fit(du.data[x_names], du.data[y_name])
modelos_a_probar[1][1].fit(du.data[x_names], du.data[y_name])
SVR()
y_predict = {}
y_predict['SGD'] = modelos_a_probar[0][1].predict(du.data[x_names])
y_predict['SVR'] = modelos_a_probar[1][1].predict(du.data[x_names])
graficar.scatter(pd.DataFrame({'y_real': du.data[y_name], 'y_pred': y_predict['SGD']}), x='y_real', y='y_pred').show()
Se seleccionan solo los mejores modelos para realizar el ajuste de hiperparametros, ya que tiene una carga computacional alta.
Al final se obtienen los parametros del mejor modelo
Tomar los parametros obtenidos en el paso anterior, se crea el modelo con esos pararmetros y se entrena el modelo con todos los datos del Train -set
Finalmente se realiza la evaluacion (segun su problema si es de regresion o de clasificacion) usando el Test - set para definir si el modelo obtenido esta bien. Compare los resultados con el Train -set vs los resultados con el Test - set
Con el análisis básico y el ajuste hecho, comienza el trabajo real (ingeniería).
El último paso para poner en produccion el modelo de prediccion sera:
El predictor final se puede serializar y grabar en el disco, de modo que la próxima vez que lo usemos, podemos omitir todo el entrenamiento y usar el modelo capacitado directamente:
#import pickle # Esta es una libreria de serializacion nativa de python, puede tener problemas de seguridad
from joblib import dump # libreria de serializacion
# garbar el modelo en un archivo
#dump(Modelo_final, 'Nombre_Archivo_Modelo.joblib')
https://medium.com/@joserzapata/paso-a-paso-en-un-proyecto-machine-learning-bcdd0939d387
Proyecto de Principio a Final sobre readmision de pacientes con Diabetes
a-starter-pack-to-exploratory-data-analysis-with-python-pandas-seaborn-and-scikit-learn
a-data-science-for-good-machine-learning-project-walk-through-in-python-part-one
Docente: Jose R. Zapata